package com.apes.framework.util;


import com.apes.framework.api.SpringManager;
import com.apes.framework.jpa.ApesBean;
import com.apes.framework.jpa.repository.CustomRepository;
import com.apes.framework.jpa.repository.ExtendedJpaRepository;
import javassist.util.proxy.ProxyObject;
import lombok.Data;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.orm.jpa.EntityManagerFactoryInfo;

import javax.persistence.EntityManager;
import javax.persistence.spi.PersistenceUnitInfo;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;


/**
 * 功能：Jpa工具类
 *
 * @author xul
 * @create 2018-02-06 8:50
 */
public class JpaUtil {

    /**
     * 获取领域对象的Repository
     */
    public static <T extends CustomRepository> T getRepository(Class type) {
        return getRepository(type.getName());
    }

    /**
     * 获取领域对象（全路径类名）的Repository
     */
    public static <T extends CustomRepository> T getRepository(String domainTypeName) {
        CustomRepositoryMetadata metadata = getCustomRepositoryMetadata(domainTypeName);
        if (metadata == null) return null;
        return (T) metadata.getRepository();
    }

    public static <T extends CustomRepository> T getRepositoryByName(String repositoryName) {
        return (T) SpringManager.getBean(repositoryName);
    }

    public static CustomRepository getCustomRepository(String businessType) {
        Class clazz = JpaUtil.getDomainClass(businessType);
        if (clazz == null) {
            throw new RuntimeException("业务模型：" + businessType + " 系统中不存在！");
        }
        CustomRepository repository = JpaUtil.getRepository(clazz.getName());
        if (repository == null) {
            throw new RuntimeException("业务模型：" + businessType + " 没有定义 Repository ！");
        }
        return repository;
    }

    /**
     * 功能：获取实体的主键
     */
    public static <T> T getId(ApesBean domain) {
        Class domainType = getDomainClass(domain);
        return (T) getJpaEntityInformation(domainType).getId(domain);
    }

    /**
     * 创建领域对象实例
     */
    public static Object newEntityInstance(String domainTypeName) {
        Object target = null;
        Class domainType = getDomainClass(domainTypeName);
        if (domainType == null) return null;
        try {
            target = domainType.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return target;
    }

    /**
     * 获取实体的领域类
     */
    public static Class getDomainClass(String classSimpleName) {
        String className = getDomainTypeName(classSimpleName);
        CustomRepositoryMetadata metadata = getCustomRepositoryMetadata(className);
        if (metadata == null) return null;
        return metadata.getRepositoryMetadata().getDomainType();
    }

    private static String getDomainTypeName(String domainTypeName) {
        return getAllDomainType().stream().filter(name -> name.substring(name.lastIndexOf(".") + 1).equals(domainTypeName.substring(domainTypeName.lastIndexOf(".") + 1))).findAny().orElse(null);
    }

    /**
     * 获取实体的领域类
     */
    public static Class getDomainClass(ApesBean entity) {
        Class domainType = entity.getClass();
        if (ProxyObject.class.isAssignableFrom(entity.getClass())) {
            domainType = ((JavassistLazyInitializer) ((ProxyObject) entity).getHandler()).getPersistentClass();
        } else if (HibernateProxy.class.isAssignableFrom(entity.getClass())) {
            domainType = ((HibernateProxy) entity).getHibernateLazyInitializer().getPersistentClass();
        }
        return domainType;
    }

    /**
     * 获取代理对象的领域实体
     */
    public static Object getImplementation(Object proxy){
        Object domain = proxy;
        if (ProxyObject.class.isAssignableFrom(proxy.getClass())) {
            domain = ((JavassistLazyInitializer) ((ProxyObject) proxy).getHandler()).getImplementation();
        } else if (HibernateProxy.class.isAssignableFrom(proxy.getClass())) {
            domain = ((HibernateProxy) proxy).getHibernateLazyInitializer().getImplementation();
        }
        return domain;
    }

    public static Serializable getId(String domainTypeName, String id) {
        String className = getDomainTypeName(domainTypeName);
        CustomRepositoryMetadata metadata = getCustomRepositoryMetadata(className);
        if (metadata == null) return null;
        Class idType = metadata.getRepositoryMetadata().getIdType();
        if (Integer.class.isAssignableFrom(idType)) return Integer.valueOf(id);
        if (Long.class.isAssignableFrom(idType) || long.class.isAssignableFrom(idType)) return Long.valueOf(id);
        if (String.class.isAssignableFrom(idType)) return id;
        throw new RuntimeException("类型错误：" + idType.getName());
    }

    public static String getIdToStr(ApesBean entity) {
        Object id = getId(entity);
        if (id == null) return null;
        if (id instanceof String) return (String) id;
        if (id.getClass().isPrimitive()) return String.valueOf(id);
        return id.toString();
    }

    private static CustomRepositoryMetadata getCustomRepositoryMetadata(String domainTypeName) {
        List<CustomRepositoryMetadata> repositoryMetadatas = getCustomRepositoryMetadatas();
        repositoryMetadatas.removeIf(m -> m == null);
        CustomRepositoryMetadata metadata = repositoryMetadatas.stream().filter(m -> m.getRepositoryMetadata().getDomainType().getName().equals(domainTypeName)).findFirst().orElse(null);
        if (metadata == null) metadata = createCustomRepositoryMetadata(domainTypeName);
        return metadata;
    }

    public static List<String> getAllDomainType() {
        EntityManagerFactoryInfo emfi = SpringManager.getBean(EntityManagerFactoryInfo.class);
        PersistenceUnitInfo pui = emfi.getPersistenceUnitInfo();
        return pui.getManagedClassNames();
    }

    private static CustomRepositoryMetadata createCustomRepositoryMetadata(String domainTypeName) {
        if (getAllDomainType().stream().filter(name -> name.equals(domainTypeName)).count() == 0) return null;
        Class domainType = ReflectUtil.getClass(domainTypeName);
        CustomRepository customRepository = createCustomRepository(domainType);
        final Class finalDomainType = domainType;
        RepositoryMetadata repositoryMetadata = new RepositoryMetadata() {
            @Override
            public Class<? extends Serializable> getIdType() {
                return getJpaEntityInformation(finalDomainType).getIdType();
            }

            @Override
            public Class<?> getDomainType() {
                return finalDomainType;
            }

            @Override
            public Class<?> getRepositoryInterface() {
                return null;
            }

            @Override
            public Class<?> getReturnedDomainClass(Method method) {
                return null;
            }

            @Override
            public CrudMethods getCrudMethods() {
                return null;
            }

            @Override
            public boolean isPagingRepository() {
                return false;
            }

            @Override
            public Set<Class<?>> getAlternativeDomainTypes() {
                return null;
            }

            @Override
            public boolean isReactiveRepository() {
                return false;
            }
        };
        String repositoryName = StringUtil.toLowerCaseFirst(domainType.getSimpleName()) + "Repository";
        CustomRepositoryMetadata metadata = new CustomRepositoryMetadata(repositoryName, customRepository, repositoryMetadata);
        customRepositoryMetadatas.add(metadata);
        return metadata;
    }

    private static JpaEntityInformation getJpaEntityInformation(Class finalDomainType) {
        EntityManager em = SpringManager.getBean(EntityManager.class);
        return JpaEntityInformationSupport.getEntityInformation(finalDomainType, em);
    }

    private static CustomRepository createCustomRepository(Class domainType) {
        JpaEntityInformation information = getJpaEntityInformation(domainType);
        EntityManager em = SpringManager.getBean(EntityManager.class);
        CustomRepository repository = new ExtendedJpaRepository(information, em);
        return repository;
    }

    private static List<CustomRepositoryMetadata> customRepositoryMetadatas;

    private synchronized static List<CustomRepositoryMetadata> getCustomRepositoryMetadatas() {
        if (customRepositoryMetadatas == null) {
            customRepositoryMetadatas = new ArrayList<>();
            SpringManager.getBeansOfType(CustomRepository.class).forEach((k, v) -> {
                Class<?> type = SpringManager.getType(k);
                RepositoryMetadata metadata = AbstractRepositoryMetadata.getMetadata(type);
                customRepositoryMetadatas.add(new CustomRepositoryMetadata(k, v, metadata));
            });
        }
        return customRepositoryMetadatas;
    }

    @Data
    static class CustomRepositoryMetadata {
        private String repositoryName;
        private RepositoryMetadata repositoryMetadata;
        private CustomRepository repository;

        public CustomRepositoryMetadata(String repositoryName, CustomRepository repository, RepositoryMetadata repositoryMetadata) {
            this.repositoryName = repositoryName;
            this.repository = repository;
            this.repositoryMetadata = repositoryMetadata;
        }
    }
}
